# -*- encoding: utf-8 -*-

# Copyright 2010 István Szentandrási
# <etude-music-player@googlegroups.com>

# This file is part of Etude.
#
# Etude is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as 
# published by the Free Software Foundation, either version 3 
# of the License, or (at your option) any later version.
#
# Etude is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Etude. If not, see <http://www.gnu.org/licenses/>.

"""
Media library browser - for now 2 paned
on the left side tree browser with 2 levels, on the right the songs that 
match the selection on the left side

so we have 2 models - on for the tree and one for the 
list of songs
"""
from . import _, PropertyOrderFrame
_b = __builtins__['_']


import gobject
import gtk

import etude.client as ec
import etude.config as ecfg
import etude.gui as eg
import etude.plugins as ep
import common

class MediaLib(ep.PluginPanel):

    _type = ep.E_PANEL_TYPE_MEDIALIB
    _short_name = "Medialib"
    _show_name = _("MediaLib")
    _short_descr = _("XMMS2 MediaLib browser")
    _version = "0.1"
    _unique = "Default_Medialib-0.1"
    _title = _("MediaLib")
    _config = { 'list': ' <title>|<artist>|<album>|<url>|<duration>',
            'tree': '<artist>|<album>|<tracknr>|<title>|<url>'
            }
    #in the tree only the first two are showed, others are used to
    #define the initial sorting of the songlist

    def __init__(self, control):
        ep.PluginPanel.__init__(self, control)
        self.client = control.client
        self.config = control.config
        self.set_shadow_type(gtk.SHADOW_NONE)

        self.hpaned = gtk.HPaned()
        self.add(self.hpaned)
        icon_theme = gtk.icon_theme_get_default()
        icon_size_menu = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)[0]
        self.songlist = eg.CollectionView(self, self.get_config('list'),
                sortable=True)

        try:
            self.pixbufs = { 
                    'artist': icon_theme.load_icon('stock_people',
                        icon_size_menu, 0),
                    'album': icon_theme.load_icon('gnome-dev-cdrom-audio',
                        icon_size_menu, 0)
                    }
        except:
            self.pixbufs = {}

        frame1 = gtk.VBox()
        scroll1 = gtk.ScrolledWindow()
        scroll1.set_shadow_type(gtk.SHADOW_IN)
        scroll1.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll1.set_size_request(150, -1)
        frame1.pack_end(scroll1)
        self.hpaned.pack1(frame1, resize = False, shrink = True)

        self.mediatree = gtk.TreeView()
        sel = self.mediatree.get_selection()
        sel.set_mode( gtk.SELECTION_MULTIPLE)
        sel.connect('changed', self.mediatree_selection_changed)
        self.mediatree.set_headers_visible(False)
        
        self.__create_mediatree_model()
        self.__fill_mediatree_model()
        
        self.__create_mediatree_columns()
        
        scroll1.add(self.mediatree)

        f1_hbox = gtk.HBox()
        frame1.pack_start(f1_hbox, False)
        media_combo_model = gtk.ListStore(gobject.TYPE_STRING,
                gobject.TYPE_STRING)
        media_combo = gtk.ComboBox(media_combo_model)
        f1_hbox.pack_start(media_combo, True, True)
        cell = gtk.CellRendererText()
        media_combo.pack_start(cell, True)
        media_combo.add_attribute(cell, 'text', 0)

        curr = self.get_config('tree')
        self.songlist.set_default_order_format(curr)
        media_combo_model.append( 
                (_b(self.tree_struct[0]).capitalize()+"/"+
                    _b(self.tree_struct[1]).capitalize(),
                    curr
                    )
                )
        #@TODO clean this up - change it, so at least the saved is shown in the list
        if curr !=  '<artist>|<album>|<tracknr>|<title>|<url>':
            media_combo_model.append( 
                    (_b(ec.XMEP_ARTIST).capitalize() + "/" +
                        _b(ec.XMEP_ALBUM).capitalize(), 
                        '<artist>|<album>|<tracknr>|<title>|<url>')
                    )
        if curr != '<album>|<artist>|<tracknr>|<title>|<url>':
            media_combo_model.append( 
                    (_b(ec.XMEP_ALBUM).capitalize() + "/" + 
                        _b(ec.XMEP_ARTIST).capitalize(), 
                        '<album>|<artist>|<tracknr>|<title>|<url>')
                    )
        media_combo_model.append( (_("Other")+"...", "other"))
        media_combo.set_active(0)
        media_combo.prev_active = media_combo.get_active_iter()
        media_combo.connect('changed', self.media_tree_combo_changed)

        #refresh button
        button = gtk.Button()
        button.set_image(gtk.image_new_from_stock(gtk.STOCK_REFRESH,
            gtk.ICON_SIZE_BUTTON))
        f1_hbox.pack_end(button, False, False)
        button.connect('clicked', self.rehash_medialib)
        
        #import button
        add_button = gtk.Button()
        add_button.set_image(gtk.image_new_from_stock(gtk.STOCK_OPEN,
            gtk.ICON_SIZE_BUTTON))
        add_button.connect('clicked', self.show_import_popup)
        f1_hbox.pack_end(add_button, False, False)

        #second panel - songlist

        f2_hbox_top = gtk.HBox()
        ext_label = gtk.Label(_("Filter")+":")
        f2_hbox_top.pack_start(ext_label, False, False)
        self.ext_entry = gtk.Entry()
        self.ext_entry.set_tooltip_markup(
                _("""<b>Tips on using:</b>
            The pattern is made up of expressions:
            expression: #[id], filter or logical and unary operation
            filter: [property][operator][value]
                : - equals      ~ - match
                &lt; - smaller     &gt; - greater
                &lt;= - smaller equals
                &gt;= - greater equals
            logical operations: 
                AND, OR
            unary operations: 
                NOT [expression]
                in: [collection]
                +[property] - property is set
            ( ) can be used for grouping

        <b>Examples:</b>
            Match everything from P!nk and Queen - 
                '(artist:Pink) OR (artist:Queen)'

        <b>Note</b>
            If something is selected in the tree 
            this works only as an additional filter."""))
        f2_hbox_top.pack_start(self.ext_entry, True, True)
        filter_but = gtk.Button()
        filter_but.set_image(gtk.image_new_from_stock(gtk.STOCK_APPLY,
            gtk.ICON_SIZE_BUTTON))
        filter_but.connect('clicked', lambda wid: self.ext_entry.activate())
        f2_hbox_top.pack_start(filter_but, False, False)
        self.ext_entry.connect('activate', self.apply_extra_filter)

        scroll2 = gtk.ScrolledWindow()
        scroll2.set_shadow_type(gtk.SHADOW_IN)
        scroll2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll2.set_size_request(400, -1) 

        self.songlist.set_rules_hint(True)
        self.songlist.get_selection().set_mode( gtk.SELECTION_MULTIPLE)
        self.songlist.enable_dnd(dest = False, src = True)
        self._last_collection = None
        
        f2_vbox = gtk.VBox()
        
        f2_vbox.pack_start(f2_hbox_top, False, False)

        scroll2.add(self.songlist)
        f2_vbox.pack_start(scroll2)

        f2_hbox = gtk.HBox()
        f2_vbox.pack_start(f2_hbox, False, False)
        
        self.hpaned.pack2(f2_vbox, resize = True, shrink = True)

        #new button

        new_coll_button = gtk.Button()
        new_coll_button.set_image(gtk.image_new_from_stock(gtk.STOCK_SAVE,
            gtk.ICON_SIZE_BUTTON))
        new_coll_button.connect('clicked', self.show_new_coll_popup)
        f2_hbox.pack_start(new_coll_button, False, False)

        #add to button

        add_to_button = gtk.Button()
        add_to_button.set_image(gtk.image_new_from_stock(gtk.STOCK_ADD,
            gtk.ICON_SIZE_BUTTON))
        add_to_button.connect('clicked', self.show_add_to_popup)
        f2_hbox.pack_start(add_to_button, False, False)

        #remove button

        remove_button = gtk.Button()
        remove_button.set_image(gtk.image_new_from_stock(gtk.STOCK_REMOVE,
            gtk.ICON_SIZE_BUTTON))
        remove_button.connect('clicked', self.show_remove_popup)
        f2_hbox.pack_start(remove_button, False, False)

        #when the mediainfo reading stops we check for changes...
        self.bind(self.client, 'e_mediainfo_reader_status',
                self.mediainfo_status_cb)


        self.show_all()
        self.songlist.connect('key_press_event', self.on_list_button_press)
        self.songlist.connect("row-activated", self.song_activated)
    
    def song_activated(self, widget, path, column):
        self.client.playlist.add_id(self.songlist.list_model[path][0])
        self.client.playlist.list_entries(cb=self._song_activated)

    def _song_activated(self, lst):
        """a song was double clicked"""
        self.client.playlist.set_next(len(lst)-1, fast=True, play=True)

    def on_list_button_press(self, widget, event, data = None):
        """add support for delete button on the songlist"""
        if event.keyval == gtk.keysyms.Delete:
            self.remove_entries(widget, selected = True)
        return False

    def mediainfo_status_cb(self, widget, running):
        """if new files are added to a media library, or refresh was
        pressed update the browser tree"""
        try:
            if not self.__rehash:
                return
        except:
            return
        if not running: #finished checking
            self.tree_model.clear()
            self.__fill_mediatree_model()
            self.__rehash = False

    def __create_mediatree_model(self):
        """create the model for the medialib browser"""
        self.tree_struct = list(ecfg.parse_column_xmms(self.get_config('tree')))
        self.tree_model = gtk.TreeStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING)
        self.mediatree.set_model(self.tree_model)

    def __fill_mediatree_model(self):
        """fill the medialib browser model"""
        cl = ec.Collection(self.client)
        coll = cl.Universe()
        first = self.tree_struct[0]
        cl.query_infos( fields = [first,], coll = coll, groupby = [first,],
                order = [first,], cb = self.__fill_tree_cb)

    def __fill_tree_cb(self, tree_val):
        first = self.tree_struct[0]
        second = self.tree_struct[1]
        try:
            pixb = self.pixbufs[first]
        except KeyError:
            pixb = None
        cl = ec.Collection(self.client)
        coll = cl.Universe()
        for val in tree_val:
            value = val[first]
            #filter using the first info
            if value == None: 
                value = "#"+_b("Unknown")
                c = cl.Complement(cl.Has(coll, first))
            else:
                c = cl.Equals(coll, field = first, value = str(value),
                        **{'case-sensitive': 'true'})
            parent = self.tree_model.append(None, (pixb, value))
            cl.query_infos( fields = [second,], groupby = [second,],
                    order = [second,],
                    cb = self.__fill_tree_lists_cb,
                    cb_args = {'parent': parent}, 
                    coll = c
                    )

    def __fill_tree_lists_cb(self, result, parent):
        """fill the secondary criteria"""
        second = self.tree_struct[1]
        try:
            pixb = self.pixbufs[second]
        except KeyError:
            pixb = None
        for val in result:
            value = val[second]
            if value == None:
                value = "#"+_b("Unknown")
            self.tree_model.append(parent, (pixb, value))

    def __create_mediatree_columns(self):
        """create column for the medialib browser"""
        #tree columns
        coll = gtk.TreeViewColumn()
        render_pixbuf = gtk.CellRendererPixbuf()
        coll.pack_start(render_pixbuf, expand = False)
        coll.add_attribute(render_pixbuf, 'pixbuf', 0)
        render_text = gtk.CellRendererText()
        coll.pack_start(render_text, expand = True)
        coll.add_attribute(render_text, 'text', 1)
        self.mediatree.append_column(coll)

    def mediatree_selection_changed(self, treeselection, data = None):
        """if the selection changed, filter the list of songs showed"""
        #check if all is selected
        row_count = treeselection.count_selected_rows()
        c = ec.Collection(self.client)
        full_selection = False
        if (row_count > 0)and (row_count >= len(self.tree_model)):
            row_iter = self.tree_model.get_iter_first()
            full_selection = True
            while row_iter:
                if not treeselection.iter_is_selected(row_iter):
                    full_selection = False
                    break
                row_iter = self.tree_model.iter_next(row_iter)
        if full_selection:
            coll = c.Universe()
        else:
            cols = []
            treeselection.selected_foreach(self._iter_selected_rows, cols)
            if cols == []:
                coll = c.IDList()
            elif len(cols) == 1:
                coll = cols[0]
            else:
                coll = c.Union(*cols)
        try:
            text = self.ext_entry.get_text()
        except AttributeError:
            text = ''
        if len(text) > 0:
            try:
                cl2 = c.coll_parse(text)
                if row_count == 0:
                    coll = cl2
                else:
                    coll = c.Intersection(coll, cl2)
                self.set_filter_error(False)
            except ValueError: #couldn't parse
                self.set_filter_error(True)
        self._last_collection = coll
        self.songlist.set_coll(coll, self.tree_struct)
        return False
    
    def set_filter_error(self, error):
        """set the filter text color to red, if the pattern added could not been
        parsed"""
        if error:
            self.ext_entry.modify_text( gtk.STATE_NORMAL,
                    gtk.gdk.color_parse("red"))
        else:
            self.ext_entry.modify_text(gtk.STATE_NORMAL, None)

    def _iter_selected_rows(self, treemodel, path, row_iter, cols):
        """iter through the selected rows, and return 
        the appropriate collection structure"""
        try:
            if len(path) == 2:
                if self.mediatree.get_selection().path_is_selected((path[0],)):
                    return
        except IndexError:
            pass
        c = ec.Collection(self.client)
        first = self.tree_struct[0]
        second = self.tree_struct[1]
        #text is in the second column - first are pic
        value = treemodel.get_value(row_iter, 1)
        if len(path) == 1:
            #when the first level was selected
            if value == ("#"+_b("Unknown")):
                coll = c.Complement(c.Has(c.Universe(), first))
            else:
                coll = c.Equals(None, field = first, value = str(value),
                        **{'case-sensitive': 'true'})
        elif len(path) == 2:
            parent_value = treemodel.get_value(treemodel.iter_parent(row_iter), 1)
            if parent_value == ("#"+_b("Unknown")):
                coll = c.Complement(c.Has(c.Universe(), first))
            else:
                coll = c.Equals(None, field = first, value = str(parent_value),
                        **{'case-sensitive': 'true'})
            if value == ("#"+_b("Unknown")):
                coll = coll & c.Complement(c.Has(c.Universe(), second))
            else:
                coll = coll & c.Equals(None, field = second,
                        value = str(value), **{'case-sensitive': 'true'})
        cols.append(coll)

    def media_tree_combo_changed(self, combo, data = None):
        """change the structure of the browser"""
        curr = self.get_config('tree')
        model = combo.get_model()
        active_path = (combo.get_active(),)
        active_iter = model.get_iter(active_path)
        active = model.get_value(active_iter, 1) #on the second column is the pattern
        if (active == curr) or active_iter == combo.prev_active:
            return False
        if active == "other":
            dialog = MTreeOrderDialog(self.get_config('tree'),
                    self.get_toplevel())
            result = dialog.run()
            if result == gtk.RESPONSE_OK:
                name, pattern = dialog.get_selection()
                new_iter = model.prepend( (name, pattern) )
                combo.set_active_iter(new_iter)
            else:
                combo.set_active_iter(combo.prev_active)
            dialog.destroy()
            return False
        else:
            self.set_config('tree', active)
        self.songlist.set_default_order_format(active)
        self.__create_mediatree_model()
        self.__fill_mediatree_model()
        combo.prev_active = active_iter
        return False

    def rehash_medialib(self, button, data = None):
        """tell the daemon to rehash the media library"""
        self.__rehash = True
        self.client.medialib.rehash()
    
    def __menu_position_setter(self, menu, widget):
        """for buttons which popup a menu, to set a menu position"""
        x, y = widget.get_toplevel().get_position()
        alloc = widget.get_allocation()
        x += alloc.x + int(alloc.width/2)
        y += alloc.y + int(alloc.height)
        return (x, y, True)
    
    def show_import_popup(self, widget, data=None):

        menu = gtk.Menu()
        item = gtk.MenuItem(label= _("Import files/folders"))
        item.connect('activate', self.filechooser)
        menu.append(item)
        
        item = gtk.MenuItem(label= _("Import url"))
        item.connect('activate', self.urlchooser)
        menu.append(item)

        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)

    def show_new_coll_popup(self, widget, data = None):
        """save the collection the server"""
        def new_coll_popup_response(dlg, response_id):
            if response_id == gtk.RESPONSE_OK:
                name = dlg.check_name()
                if not name:
                    return False
                namespace = dlg.get_ns()
                coll = dlg.get_coll(get_ids_callback=self.songlist.get_ids)
                if coll:
                    c = ec.Collection(self.client)
                    c.save(coll, name, namespace)
            dlg.destroy()
        dialog = common.CollectionSaverDialog(self, self._last_collection)
        dialog.connect('response', new_coll_popup_response)
        dialog.show()
    
    def show_add_to_popup(self, widget, data = None):

        menu = common.create_add_to_playlist_menu(self._add_to_playlist,
                self.client)
        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)

    def _add_to_playlist(self, widget, playlist, selected):
        if playlist == None:
            playlist = "_active"
        if selected:
            ids = self.songlist.get_selected_ids()
            for song_id in ids:
                self.client.playlist.add_id(song_id, playlist = playlist)
        else:
            if self._last_collection:
                self.client.playlist.add_collection(self._last_collection,
                        self.songlist.get_order(), playlist)
            

    def show_remove_popup(self, widget, data = None):

        menu = gtk.Menu()
        item = gtk.MenuItem(label= _("Remove selected from MediaLib"))
        item.connect('activate', self.remove_entries, True)
        menu.append(item)
        
        item = gtk.MenuItem(label= _("Remove all from MediaLib"))
        item.connect('activate', self.remove_entries, False)
        menu.append(item)

        menu.show_all()
        menu.popup(None, None, self.__menu_position_setter, 1, 0, widget)
            
    def filechooser(self, widget, data = None):
        """showa file chooser and import the results"""
        paths = common.filechooser(self.get_toplevel(), self.control)
        for isdir, path in paths:
            if isdir:
                self.client.medialib.path_import(path)
            else:
                self.client.medialib.add_entry(path)
        if len(paths)>0:
            self.__rehash = True
        return False
    
    def urlchooser(self, widget, data = None):
        entry_text = common.url_chooser_dialog(self.get_toplevel())
        if entry_text:
            self.client.medialib.add_entry(entry_text)
        return False
    
    def remove_entries(self, widget, selected = True):
        """
        selected decides if all the songs in the current listing are deleted, 
        or just the selected
        """
        ids = []
        list_model = self.songlist.get_model()
        if selected == False: #remove all songs from the 
            """
            iter = list_model.get_iter_first()
            while iter != None:
                ids.append(list_model.get_value(iter, 0))
                iter = list_model.iter_next(iter)
            """
            ids = self.songlist.ids
        else: #remove only the selected ones
            sel = self.songlist.get_selection()
            rows = [list_model.get_iter(path)
                    for path in sel.get_selected_rows()[1]]
            ids = [list_model.get_value(row_iter, 0) for row_iter in rows]
        for song_id in ids:
            self.client.medialib.remove_entry(song_id)
        self.apply_extra_filter()
        return False

    def apply_extra_filter(self, widget = None, data= None):
        """apply the extra filter on the current collection, or if nothing 
        was selecting on the browser on the whole medialib"""
        self.mediatree_selection_changed(self.mediatree.get_selection())
    
    def show_preferences(self, parent):
        """
        Show preferences for collections panel
        """
        dialog = gtk.Dialog(title = _("Medialib browser settings"),
                parent = parent,
                flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
                    gtk.STOCK_APPLY, gtk.RESPONSE_OK)
                )
        dialog.set_default_size(300, 60)
        #inputfield
        entry = gtk.Entry()
        entry.set_text(self.get_config('list'))
        #allow using enter
        entry.connect('activate',
                lambda widget: dialog.response(gtk.RESPONSE_OK))
        entry.set_tooltip_text(common.LIST_STRUCT_TOOLTIP)
        table = gtk.Table(rows = 2, columns = 1)
        table.attach(gtk.Label(_("Columns format")+":"), 0, 1, 0, 1,
                xoptions = 0, yoptions = 0)
        table.attach(entry, 0, 1, 1, 2, xoptions = gtk.FILL|gtk.EXPAND,
                yoptions = 0)
        dialog.vbox.pack_start(table, True, True)
        dialog.show_all()
        res = dialog.run()
        if res == gtk.RESPONSE_OK:
            view_format = entry.get_text()
            self.set_config('list', view_format)
            self.songlist.set_format(view_format)
        dialog.destroy()

        
class MTreeOrderDialog(gtk.Dialog):

    """Dialog for custom browser tree"""
    called = 0

    def __init__(self, current_patt, parent):
        gtk.Dialog.__init__(self, title = _("MediaLib Tree order"),
                parent = parent,
                flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
                    gtk.STOCK_APPLY, gtk.RESPONSE_OK))
        self.set_default_size(300, 300)
        print(current_patt)
        self.prop_frame = PropertyOrderFrame(current_patt)
        self.vbox.pack_start(self.prop_frame, True, True)
        self.show_all()

    def get_selection(self):
        """
        returns a tuple with a (name, pattern)
        """
        #TODO change to some useful name
        MTreeOrderDialog.called += 1
        ret = self.prop_frame.get_selection()
        return (_("Custom")+str(MTreeOrderDialog.called), ret)

